//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2013 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_AUTO_MATH_H_
#define _BUOLA_AUTO_MATH_H_

#include <buola/auto/traits.h>
#include <cmath>
#include <limits>
#include <algorithm>

namespace buola {

///\addtogroup math
///@{

using std::abs;
using std::signbit;
using std::sin;
using std::cos;
using std::pow;
using std::log;
using std::exp;
using std::sqrt;
using ::log1p;
using std::ceil;
using std::floor;
using std::fmod;
using std::min;
using std::max;

///\name Arithmetic
//@{

template<typename tType>
constexpr tType constexpr_min(tType p1,tType p2)
{
    return p1<p2?p1:p2;
}

template<typename tType>
constexpr tType constexpr_max(tType p1,tType p2)
{
    return p1<p2?p2:p1;
}

///returns the minimum of all the arguments
template<typename tType>
const tType& min_n(const tType &p1,const tType &p2)
{
    return p1<p2?p1:p2;
}


template<typename tType,typename... tArgs>
const tType& min_n(const tType &p1,const tType &p2,const tArgs&... pArgs)
{
    return min_n(p1,min_n(p2,pArgs...));
}

///returns the maximum of all the arguments
template<typename tType>
const tType& max_n(const tType &p1,const tType &p2)
{
    return p1<p2?p2:p1;
}

template<typename tType,typename... tArgs>
const tType& max_n(const tType &p1,const tType &p2,const tArgs&... pArgs)
{
    return max_n(p1,max_n(p2,pArgs...));
}

///find the median of the three values \p a, \p b and \p c
template<typename tType>
inline const tType &median(const tType &a,const tType &b,const tType &c)
{
    if(a<b)
    {
        if(b<c)
            return b;
        else if(a<c) 
            return c;
        else
            return a;
    }
    else
    {   
        if(a<c)
            return a;
        else if(b<c) 
            return c;
        else
            return b;
    }
}

///sorts the two values \p a and \p b using one comparison and possibly one swap
template<typename tType>
inline void sort2(tType &a,tType &b)
{
    using std::swap;
    if(a>b)
        swap(a,b);
}

///sorts the three values \p a, \p b and \p c using at most two swaps and three comparisons
template<typename tType>
inline void sort3(tType &a,tType &b,tType &c)
{
    using std::swap;
    if(a<=b)
    {
        if(c<b)
        {
            swap(b,c);
            if(b<a)
                swap(a,b);
        }
    }
    else if(c<=b)
    {
        swap(a,c);
    }
    else
    {
        swap(a,b);
        if(c<b)
            swap(b,c);
    }
}
 
///returns \p v clamped between \p a and \p b
///
///This function is almost identical to median, except that it assumes that \p a < \p b, which saves a comparison
template<typename tType>
inline const tType &clamp(const tType &v,const tType &a,const tType &b)
{
    if(v>b)
        return b;
    else if(v<a) 
        return a;
    else
        return v;
}
 
///returns \p n rounded up to the closest multiple of \p r
///
///The arguments must be of integer type, and they can be of different type
template<typename tInt1,typename tInt2>
inline tInt1 round_up_to_multiple(tInt1 n,tInt2 pr)
{
    tInt1 r=(tInt1)pr;
    return (n+r-1-(n+r-1)%r);
}

///returns \p n rounded down to the closest multiple of \p r
///
///The arguments must be of integer type, and they can be of different type
template<typename tInt1,typename tInt2>
inline tInt1 round_down_to_multiple(tInt1 n,tInt2 pr)
{
    tInt1 r=(tInt1)pr;
    return (n-n%r);
}

///divides \p pLH by \p pRH, rounding up
///
///The arguments must be of integer type
template<typename tInt>
inline tInt div_round_up(tInt pLH,tInt pRH)
{
    return (pLH+pRH-1)/pRH;
}

//@}
    
/// \name Angles
//@{

///converts from degrees to radians
inline constexpr double deg2rad(double pA)
{
    return pA*M_PI/180.0;
}

///converts from radians to degrees
inline constexpr double rad2deg(double pA)
{
    return pA*180.0/M_PI;
}

///literal which is automatically converted to radians
inline constexpr long double operator"" _deg(long double pA)
{
    return pA*M_PI/180.0;
}

///literal which is automatically converted to radians
inline constexpr long double operator"" _deg(unsigned long long int pA)
{
    return pA*M_PI/180.0;
}

///normalize angle to interval [0,2*PI)
inline double normalize_angle(double pA)
{
    return pA-2.0*M_PI*floor(pA/(2.0*M_PI));
}

///checks if angle pA is between p1 and p2
inline bool is_angle_between(double pA,double p1,double p2)
{
    return (normalize_angle(pA-p1)<=p2-p1);
}

///returns the difference between the two angles (p2-p1) normalized to the interval [0,2*PI)
inline double angle_diff(double p1,double p2)
{
    return normalize_angle(p2-p1);
}

///returns 1 if the number is positive, -1 if it is negative and 0 if it is 0
template<typename tSign,typename tReal>
inline tSign get_sign(tReal pValue)
{
    if(pValue>0)
        return 1;
    else if(pValue<0)
        return -1;
    else
        return 0;
}

template<typename tInt,typename std::enable_if<std::numeric_limits<tInt>::is_signed,int>::type vDummy=1>
bool is_negative(tInt pInt)
{
    return pInt<0;
}

template<typename tInt,typename std::enable_if<!std::numeric_limits<tInt>::is_signed,int>::type vDummy=1>
bool is_negative(tInt pInt)
{
    return false;
}

///returns the square of its argument
template<typename tReal>
inline OEnableIf<QIsNumeric<tReal>,tReal> sq(tReal pValue)
{
    return pValue*pValue;
}

#ifdef _GLIBCXX_HAVE_SINCOS
using ::sincos;
#else
///simultaneously obtain sine and cosine of angle
inline void sincos(double a,double *s,double *c)
{   
    *s=sin(a);
    *c=cos(a);
}
#endif

#if BUOLA_COMPILER_CLANG
///\todo change when libc++ is fixed
///returns true if the argument is NaN
inline bool is_nan(float x)             {    return std::isnan(x);      }
inline bool is_nan(double x)            {    return std::isnan(x);      }
inline bool is_nan(long double x)       {    return std::isnan(x);      }
///returns true if the argument is finite
inline bool is_finite(float x)          {    return std::isfinite(x);   }
inline bool is_finite(double x)         {    return std::isfinite(x);   }
inline bool is_finite(long double x)    {    return std::isfinite(x);   }
///returns true if the argument is not finite
inline bool is_inf(float x)             {    return std::isinf(x);      }
inline bool is_inf(double x)            {    return std::isinf(x);      }
inline bool is_inf(long double x)       {    return std::isinf(x);      }
#else
///returns true if the argument is NaN
template<typename tScalar>
inline auto is_nan(tScalar pScalar) -> decltype(std::isnan(pScalar))
{
    return std::isnan(pScalar);
}

///returns true if the argument is finite
template<typename tScalar>
inline auto is_finite(tScalar pScalar) -> decltype(std::isfinite(pScalar))
{
    return std::isfinite(pScalar);
}

///returns true if the argument is not finite
template<typename tScalar>
inline auto is_inf(tScalar pScalar) -> decltype(std::isinf(pScalar))
{
    return std::isinf(pScalar);
}
#endif

///returns the "undefined" value for the \p tScalar type
template<typename tScalar>
inline constexpr tScalar undefined()
{
    typedef std::numeric_limits<tScalar> TLimits;
    return TLimits::has_infinity?TLimits::infinity():TLimits::max();
}

///checks if \p pScalar corresponds to the undefined value for \p tScalar
template<typename tScalar>
inline bool undefined(tScalar pScalar)
{
    return pScalar==undefined<tScalar>();
}

//@}

///@}

/*namespace buola*/ }

#endif
